// Package util
// @Author zhongxc
// @Date 2024/7/30 10:07:00
// @Desc
package util

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/golang-jwt/jwt"
	"github.com/twinj/uuid"
	"net/http"
	"strconv"
	"strings"
	"time"
	"vben-api/pkg/repository"
)

type TokenDetails struct {
	AccessToken  string
	RefreshToken string
	AccessUuid   string
	RefreshUuid  string
	AtExpires    int64
	RtExpires    int64
}

type MyClaims struct {
	UserID     int64  `json:"user_id"`
	Username   string `json:"username"`
	AccessUuid string `json:"access_uuid"`
	jwt.StandardClaims
}

type MyRtClaims struct {
	UserID      int64
	Username    string
	RefreshUuid string
	jwt.StandardClaims
}

type AccessDetails struct {
	AccessUuid string
	UserId     int64
	Username   string
}

type JwtUtil struct {
	ExpireAt             time.Duration
	SignKey              string
	RefreshSignKey       string
	cache                repository.CacheRepo
	accessUuidKeyPrefix  string
	refreshUuidKeyPrefix string
}

func NewJwtUtil(ExpireAt time.Duration, SignKey string, RefreshSignKey string, cache repository.CacheRepo) *JwtUtil {
	return &JwtUtil{
		ExpireAt:             ExpireAt,
		SignKey:              SignKey,
		RefreshSignKey:       RefreshSignKey,
		cache:                cache,
		accessUuidKeyPrefix:  "account:access",
		refreshUuidKeyPrefix: "account:refresh",
	}
}

// CreateToken 创建token
func (j *JwtUtil) CreateToken(user map[string]any) (*TokenDetails, error) {
	userId, err := strconv.ParseInt(fmt.Sprintf("%v", user["user_id"]), 10, 64)
	if err != nil {
		return nil, err
	}
	userName := fmt.Sprintf("%v", user["username"])

	td := &TokenDetails{}
	td.AtExpires = time.Now().Add(time.Minute * j.ExpireAt).Unix()
	td.AccessUuid = uuid.NewV4().String()

	td.RtExpires = time.Now().Add(time.Hour * 24 * 7).Unix()
	td.RefreshUuid = uuid.NewV4().String()

	mySigningKey := []byte(j.SignKey)

	claims := MyClaims{
		userId,
		userName,
		td.AccessUuid,
		jwt.StandardClaims{
			ExpiresAt: td.AtExpires,
			IssuedAt:  userId,
			Issuer:    userName,
		},
	}

	at := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	td.AccessToken, err = at.SignedString(mySigningKey)
	if err != nil {
		return nil, err
	}

	myRefreshKey := []byte(j.RefreshSignKey)
	rtClaims := MyRtClaims{
		userId,
		userName,
		td.RefreshUuid,
		jwt.StandardClaims{
			ExpiresAt: td.RtExpires,
			IssuedAt:  userId,
			Issuer:    userName,
		},
	}
	rt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtClaims)
	td.RefreshToken, err = rt.SignedString(myRefreshKey)
	if err != nil {
		return nil, err
	}
	return td, nil
}

// CreateAuth 保存 JWT 的元数据
func (j *JwtUtil) CreateAuth(userid int64, td *TokenDetails) error {
	at := time.Unix(td.AtExpires, 0)
	rt := time.Unix(td.RtExpires, 0)
	now := time.Now()

	type AccessAuth struct {
		UserId      int64
		RefreshUuid string
	}

	accessAuth := AccessAuth{
		UserId:      userid,
		RefreshUuid: td.RefreshUuid,
	}
	strJson, _ := json.Marshal(accessAuth)
	accessUuidKey := fmt.Sprintf("%s:%s", j.accessUuidKeyPrefix, td.AccessUuid)
	errAccess := j.cache.Put(context.Background(), accessUuidKey, string(strJson), at.Sub(now))
	if errAccess != nil {
		return errAccess
	}

	type RefreshAuth struct {
		UserId     int64
		AccessUuid string
	}
	refreshAuth := RefreshAuth{
		UserId:     userid,
		AccessUuid: td.AccessUuid,
	}
	strJson, _ = json.Marshal(refreshAuth)

	refreshUuidKey := fmt.Sprintf("%s:%s", j.refreshUuidKeyPrefix, td.RefreshUuid)
	errRefresh := j.cache.Put(context.Background(), refreshUuidKey, string(strJson), rt.Sub(now))
	if errRefresh != nil {
		return errRefresh
	}
	return nil
}

// ExtractToken
// @Description: 从请求标头中提取 token
// @param r
// @return string
func (j *JwtUtil) ExtractToken(r *http.Request) string {
	bearToken := r.Header.Get("Authorization")
	strArr := strings.Split(bearToken, " ")
	if len(strArr) == 2 {
		return strArr[1]
	}
	return ""
}

// VerifyToken 验证token
func (j *JwtUtil) VerifyToken(r *http.Request) (*jwt.Token, error) {
	tokenString := j.ExtractToken(r)
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		// jwtTokenSignKey := variable.ConfigYml.GetString("Token.SignKey")
		return []byte(j.SignKey), nil
	})
	if err != nil {
		return nil, err
	}
	return token, nil
}

// TokenValid 检查此 token 的有效性，了解其仍然有用或是已过期
func (j *JwtUtil) TokenValid(r *http.Request) error {
	token, err := j.VerifyToken(r)
	if err != nil {
		return err
	}

	if _, ok := token.Claims.(*MyClaims); !ok && !token.Valid {
		return err
	}

	return nil
}

// ExtractTokenMetadata 提取 token元数据，这些元数据将在我们之前设置的 redis 存储中进行查找
func (j *JwtUtil) ExtractTokenMetadata(r *http.Request) (*AccessDetails, error) {
	token, err := j.VerifyToken(r)
	if err != nil {
		return nil, err
	}

	claims, ok := token.Claims.(*MyClaims)
	if ok && token.Valid {
		accessUid := claims.AccessUuid
		userId := claims.UserID
		userName := claims.Username
		return &AccessDetails{
			AccessUuid: accessUid,
			UserId:     userId,
			Username:   userName,
		}, nil
	}

	return nil, err
}

// FetchAuth
// @Description: 在 redis 中查找 token 元数据，如果找不到记录，则可能意味着 token 已过期，因此引发错误。
// @param authD
// @return int64
// @return error
func (j *JwtUtil) FetchAuth(authD *AccessDetails) (int64, error) {
	accessUuidKey := fmt.Sprintf("%s:%s", j.accessUuidKeyPrefix, authD.AccessUuid)
	userMeta, err := j.cache.Get(context.Background(), accessUuidKey)
	if err != nil {
		return 0, err
	}

	var AccessAuth struct {
		UserId      int64
		RefreshUuid string
	}

	_ = json.Unmarshal([]byte(userMeta), &AccessAuth)

	refreshUuidKey := fmt.Sprintf("%s:%s", j.refreshUuidKeyPrefix, AccessAuth.RefreshUuid)
	_, err = j.cache.Get(context.Background(), refreshUuidKey)
	if err != nil {
		return 0, nil
	}

	return AccessAuth.UserId, nil
}

// DeleteAuth 从redis中删除JWT元数据
func (j *JwtUtil) DeleteAuth(key string) error {
	err := j.cache.Delete(context.Background(), key)
	if err != nil {
		return err
	}
	return nil
}
